当前位置首页网游《没有内存,怎么还能跑程序呢》

《没有内存,怎么还能跑程序呢》

游戏类型:动作 枪战 武侠 俄罗斯 2008 

制作团队:本·阿弗莱克 艾莉丝·布拉加 J.D.普拉多 戴奥·奥柯奈伊 杰夫·法 

制作公司:德翁·泰勒 

游戏介绍

没有内存,怎么还能(👜)跑程序呢



主存(🤷)(RAM) 是一(🃏)件非常重要的资源,必须要小心对待内存。虽(🔶)然目(📤)前大多数内存的增长速度要比(🆎) IBM 7094 要快的多,但是,程序大小的增(💅)长要比内存的增长还快很多。正如帕(🐙)金森定律说的那样:不管(⛩)存储器有多大,但是程序大小的增长速度比内(🏨)存容量的增长速度要快的多。下面我(😫)们就来(🚜)探讨(🤥)一下(🎭)操作系统是如何创建内存并管理他们(📬)的。

经过多年的探讨,人们提出了一种 分层(🦍)存储器体系(memory hierarchy),下面(🥍)是分层体系的分类

顶层的存储器速度最高,但是容量最小,成本非常高,层级结构越向下,其访问效率越慢,容量越大,但是造价也就越便宜。

操作系统中管理内存层次结构的部(🥗)分称为内存管理器(memory manager),它的(😕)主要工作是有效的管理内存,记录哪些内存是正在使用的(🦍),在进程需要时分配内存以及在进程完成时回收内存。

下面我们会对不同的内存管理模型进行探讨,从简单到复杂,由于最低(👨)级别的缓存是由硬件进行管理的,所以我们主要探讨主存(🕰)模型和如何对主存进行管理。

无存储器抽象

最简单的存储器抽象是没(🏵)有存储。早期(🍭)大型计算机(20 世纪 60 年代之前),小型计算机(20 世纪 70 年代之前)和个人计算机(20 世纪 80 年代之前)都没有(🍑)存储器抽象。每一个程序都直接访问物(🏢)理内存。当一个程序执行如下命令:

MOV REGISTER1, 1000

计算机会把位置为 1000 的物理内存中的内容移到 REGISTER1 中。因(💗)此(🤓),那时呈现给程序员的内存模型就(🖕)是物理内存,内存地址从 0 开始到内存地址的最大值中,每个地址中都会包含一个 8 位位数的单元。

所以这种情况下的计算机不可能会有两个应用程序同时在内存(🆘)中。如果第一个程序向(💌)内存地址 2000 的这个位(🙋)置写入了一个值,那么此值将会替换第二个程序在该位置上的值,所以,同时运行(👱)两个应(🐙)用程序是行不通的(💋),两个程序会立(✈)刻崩(📡)溃。

不(🐝)过即使存储器(🛶)模型就是物理内存,还是存在一些可选项的。下面展示了三种变体

在上图 a 中,操(🥂)作系统位于 RAM(Random Access Memory) 的底部,或像是图 b 一样位于 ROM(Read-Only Memory) 顶部;而在图 c 中,设备驱动程序位于(🎯)顶端的 ROM 中,而(🔚)操作系统位于底部的 RAM 中。图 a 的模型以前用在大型机和(😩)小型机上,但(🆕)现(📕)在已经很少使用了;图 b 中的模型一般用于掌(😸)上电脑或者是嵌入式系统中。第三种模型就应用在早期个人计算(🎛)机中了。ROM 系(📐)统中的一部分成为 BIOS (Basic Input Output System)。模型 a 和 c 的缺点是用户程序中的错误可能会破坏操(🔋)作系统,可能会导致灾难性的后果。

按照这种方式组(🎦)织系统时,通常同一个时刻只能有一个线程正在运(😜)行。一旦用户键入了一个命(💪)令(♍),操作系(🤣)统就把需要的程序从磁盘复制到内存中(🐠)并执行;当进程运行结束后,操(✏)作(📯)系统在用户终端显示提示符并等待新的命令。收到新的命令后,它把新的程序装入内存,覆盖前(🤹)一个程序。

在没有存储器抽象的系统中实现并(🗡)行性的一种方式是使用多线程来编程。由于同(🚉)一进程中的多线程内部共享同一内存映像,那么实现并行也就不是问题了。

运行(😏)多个程序

但(🚩)是,即便没有存储器抽象(🌲),同时运行多个程序也是有可能的。操作系统只需要把当前内存(🎠)中所有内容保存到磁盘文件中,然后再把程序读入内存即可。只要某一时间只有一(⚡)个程序,那么就不会产生冲突。

在额外(🔋)特殊硬件的帮助下,即使没有交换功能,也可以并行的运行多个程序。IBM 360 的早期(👒)模型就是这样解决的

System/360是 IBM 在1964年4月7日,推出的划时代的大(🔓)型电脑,这一系列是世界上首(🚂)个指令集可兼容计算机。

在 IBM 360 中,内存被划分为 2KB 的区域块,每块区域被分配一个 4 位的保护键,保护键存储在 CPU 的特殊寄存器中。一个内存为 1 MB 的机器只需要 512 个这样的 4 位寄存器,容量总(🌡)共为 256 字节 (这个会算吧。) PSW(Program Status Word, 程序状态字) 中有一个 4 位码。一个运行中的进(🐥)程如果(😈)访问键与其 PSW 码不同的内存,360 硬件会发现这种情况,因为只有操作系统可以修改保护键(💱),这样就可以防止进程之间(⬆)、用户进程和操作系统之间的干扰。

这种解决方式是有(🛷)一个缺陷。如下所示,假设有两个程(👐)序,每个大小各为 16 KB

从图上可(🕜)以看出,这是两个不同的 16KB 程序的装载过程,a 程序(🐽)首(⏰)先会跳转到地址 24,那里是一条 MOV 指令,然而 b 程序会首先跳转到地址 28,地址 28 是一条 CMP 指令。这是两个程序被先后加载到内存中的情形,假如这(👪)两个程序被(🏭)同时加载到内存中从 0 地址处开始执行,内存的状态就如上面 c 图所示,程序装载完毕开始运(🐯)行,第一个程序首先从 0 地址(🚬)处开始运行,执行 JMP 24 指令,然(🏄)后依次执行(🎦)后面的指令(许多指令没(🤾)有画出),一段时间后第一个程序执行完毕,然后开始执行第二个程序。第二个程序的第一条指令是 28,这条指令会使程序跳转到第一个程序的 ADD 处,而不是事先设定的跳转指令 CMP,由于内存地址的不正确访问,这个程序可能在 1秒内就崩溃了。

上面两个程序同时执行最核心的(😗)问题是都引用了绝对物理地址。这不是我们想要看到的。我们想要的是每一个程序都会引用一个私有的本地地址。IBM 360 在第二(🔎)个程序装载到内存中的时候会使用一种称为 静态重定位(static relocation) 的技术来修改它。它的工作流程如下:当一个程序被加载到 16384 地址时,常数 16384 被加到每一个程序(🎓)地址上(所以 JMP 28会变为JMP 16412 )。虽然这个机制在不出错误的情况下是可行的,但这不是一种通用的解决办法,同时会减慢装载速度。更近一步来讲,它需要所有可执行程序(🎺)中的(📔)额外信息,以指示哪些包含(可重定位(🌝))地址,哪些不包(♒)含(可重定位)地址。毕竟,上图 b 中的 JMP 28 可以被重定向(被修改),而类似 MOV REGISTER1,28 会把(🐉)数字 28 移到 REGISTER 中则不会重定向。所以,装载器(loader)需要一定的能力来辨别地址和常数。

一种存储器抽象:地址空间

把物理内存暴露给进程会(🚌)有几个主要的缺点:第一个问题是,如果用户程序可以寻(⚡)址内存的(🧗)每个字(🎷)节,它们就(📪)可以很容易的破坏操作系统,从而使系统停止运行(除非使用 IBM 360 那种 lock-and-key 模式或者特殊的硬件进行保护(💘))。即使(🛺)在只有一个用户进程运行的情况下,这个问(🥀)题也存在。

第二点是,这种模型想(🌨)要运行多(📂)个程序是很困难的(如果只有一个 CPU 那就是顺序执行),在个人计算机(😵)上,一般会打开很多应用程序,比如输入法、电子邮件、浏览器,这些进程在(🛍)不同时刻会有一个(🕵)进程正(💅)在运行,其他应用程序可以通过鼠标来唤醒。在系统中(🦄)没有物理内存的情况下很(🏡)难实现(🌊)。

地址空间的概(👫)念

如(🚳)果要使多个应用程序同时运行在内存中,必须要解决两(⛪)个问题:保(🎙)护和 重定位。我们来看 IBM 360 是如何解决的(🌳):第一种解决方式是用(🐓)保护(🎖)密钥标记内存块,并将执行过程的密钥与提取的每个存储字(🍊)的密钥进行比较。这种方式只能解决第一种问题,但(🌻)是还是不能解决多进程在内存中同时运行的问题。

还有一种更好的方式是创(📁)造一个存储器抽象:地址空间(🍩)(the address space)。就像进程的概念创建了一种抽(🌏)象的 CPU 来运行程序,地址空间也创建了一种抽象内存供程序使用。地址空间是进程可以用来寻址内存的地址集。每个进程都有它自己的地址空间,独(🚺)立于其他进程的地址空间,但是某些进程会希望可以共享地址空间。

基址寄存器和变址寄存(😭)器

最简单的办法是使用动态重(🏨)定位(dynamic relocation),它就是通过一种简单的方式将每个进程的地址空间映射到物理内存的不同区域。从 CDC 6600(世界上最早的超级计算机(📕))到 Intel 8088(原始 IBM PC 的核心)所使用的经典办法是给每个 CPU 配置两个特殊硬件寄存器,通常叫(🎾)做基址寄存器(basic register)和变址寄存器(limit register)。当使用基址寄存器和变址寄存器使,程序会装载(🦄)到内存中连续的空(🏃)间位置(🍲)并且在装载期间无需重定位。当一个进程运行时,程序的起始(🆑)物理地址装载到基址寄存器中,程序(🎢)的长度则装载到变址寄存器中。在上图 c 中(🐽),当一个程序运行时,装(🏓)载到这些硬件寄存器中的基址和变址寄存器的值分别是 0 和 16384。当第二个程序运行时,这些值分别是 16384 和 32768。如果第三个 16 KB 的程序直接装载(🔲)到第(🍸)二个程序的地址之上并且运行,这时基址寄存器和变址(🐙)寄存器的值会是 32768 和 16384。那么我们可以(🏷)总结下

基址寄存器:存储数据内存的起始位置变址寄(🐩)存器:存储应用程序的长度(⬅)。

每当(🦂)进程引用内存以获取指令或读取或写入数据字时,CPU 硬件都会自动将基址值添加到进程生成的地址(✋)中,然后再将其发送到内存总线(🎧)上。同时,它检查(➿)程序提供的地址是否等于或大(🏷)于变址寄存器 中的(🐷)值。如果程序提供的地址要超过变址寄存器(🦆)的(🍸)范围,那么(🔩)会产生错误并中止访(🍙)问。这样,对上图 c 中执行(👫) JMP 28 这条指令后,硬件会把它解释为 JMP 16412,所以程序能够跳到 CMP 指令,过程如下

使用基址寄存(👾)器和(🛥)变址寄存器是给每个进程提供私(🎌)有地址空间的(🛑)一种非常好的方法,因为每个内存(🏙)地址在送到内存之(🚏)前,都会先(🐑)加上基址寄存器的内容。在很多实际系统中(🚹),对基址寄存器和变(🌪)址寄存器都会以一定的(✒)方式(💊)加以保护,使得只有操作系统可以修改它们。在(🥚) CDC 6600 中就提供了对这些寄存器(🥃)的保护,但在 Intel 8088 中则没(⬛)有,甚至没有变(👐)址寄存器。但是,Intel 8088 提供了许多基址寄存器,使程序的代码和数据可以被独立的重定位,但是对于超出范围的内存引用没有提供保护。

所以你可以知(🛂)道使用基址寄存器和变址寄存器的缺点,在每次访问内存时,都会进行 ADD 和 CMP 运算。比较可以执行的很快,但是加法就会相对慢一些,除非使用特殊的加法电路,否则加法因进位传播时间(🥢)而变慢。

交换技术

如果计算(🚥)机的(😩)物理内存足够(🌿)大来容纳所有的进程,那(🍤)么之前提及的方案或多或少是可行的。但是实际上,所有进程需要的 RAM 总容量要远远(📃)高(🌖)于(🗃)内存的容量。在 Windows、OS X、或者 Linux 系统中,在计算机完成启动(Boot)后,大约有 50 - 100 个进程随之启动。例如,当(🛏)一个 Windows 应用程序被安装后,它通常会发出命令,以便在后续系统启动时,将启动一个进程,这个进程除了检查应用(➡)程序的更新外不做任何操作。一个简单的应用程序可能会占用 5 - 10MB 的内(🚪)存。其他后台(🚈)进(♍)程会检查电子邮件、网络连接以及许多其他诸如此类的任务。这一切都会发生(🐷)在第一个用户启动之前。如今,像是 Photoshop 这样(🛑)的重要用户应用程序仅仅需要 500 MB 来启动,但是一旦它们(🍝)开始处理数据就需要许多 GB 来处(🚔)理。从结果上来看,将所有进程始终保持(🛣)在内存中需要大量内存,如果内(🎐)存不足,则无法完成。

所以针对上面内存不足的问题,提出了两种处理方式:最简单的一种方式就是交换(swapping)技术,即把一个进程完整的调入内存,然(📩)后再内存中(🗓)运行一(🥌)段时间,再把它放回磁盘。空闲进程会存储在磁盘中,所以这些进程在没有运行时不会占用太多内存。另外一种策略叫做虚拟内存(virtual memory),虚拟内存技术能够允许应用(👕)程序部分的(🌩)运行在(🎐)内存中。下面我们首先先探讨一下交换

交换过程

下面是一(🌋)个交换过程

刚开始的(🐵)时候,只有进程 A 在(🐧)内存中,然后从创建进程 B 和进程(🧢) C 或者从磁盘中把它们换入内存,然后在图 d 中,A 被换出内存到磁盘中,最后 A 重新进来。因为图 g 中的进程 A 现在到了不同的位置,所以在装载过程中(🔶)需要被重新定位,或者在交换程序时(📢)通过软件来执行;或者在程序执行期间通过硬件(🏨)来重定位。基址寄存器和变址寄存器就适用于这种(🦂)情况。

交换在内存创建了多个 空闲区(hole),内存会把所有的(🏗)空闲区尽可能向下移动合并成为一个大的空闲区。这项技术称为内存紧缩(memory compaction)。但是这项技术通常(🐶)不会使用,因为这项技术回消耗很多 CPU 时间。例如,在(📷)一个 16GB 内存的机器上每 8ns 复制 8 字节,它紧缩全部的内存大约要花费 16s。

有一个值得注意的问题是,当进程被创建或者换入内存时应该为它分配多大的内存。如果进程被创建后(🎱)它的大小是固定(🏁)的(🙋)并且不再(🌬)改(📻)变,那么分配策略就比(🧐)较简单:操作系统会(🛏)准确的按其需(🌔)要的大小进行分配。

但是如果进程(📍)的 data segment 能够自动增长,例如,通过动态分配堆中的内存,肯定会出现问题。这里还是再提一下什么是 data segment 吧。从逻辑层面操作系统把数据分成不同的段(不同的区域)来存储:

代码段(codesegment/textsegment):(🔫)

又称文本段,用来存(🎶)放指令,运行代码(🙎)的一块内存空间

此空间大小在代码运行前就已经确定

内(🔵)存空间一般属于只读,某些架构的代码也允许可写

在代码段中,也有可能包含一些(♎)只读的常数变量,例如字符串常量等。

数据段(datasegment):

可读可写

存储初始化的全局变量和初始化的 static 变量

数据段中数据的生存(🌍)期是随程序持续性(随进程持续性)随进程持续性:进程创建就存在,进程死亡就消失

bss段(bsssegment):(⛱)

可读可写

存储未初始化的全局变量和(😸)未初始(🐪)化的 static 变量

bss 段中数据的生存期随进程持续性

bss 段中的数据一般默认为0

rodata段:

只读数据比如 printf 语句中(㊗)的格式字符串和开关语句的跳转表。也(♏)就是常量区。例如,全局作用域中的 const int ival = 10,ival 存放在 .rodata 段;再如,函数局部作用(♟)域中的(🧘) printf("Hello world %d\n", c); 语句中的格式字符串 "Hello world %d\n",也存放在 .rodata 段。

栈(stack)(😅):

可读可写

存储的是函数或代码中的局部变量(非 static 变量)

栈的生存期随代码块持续性,代码块运行(⛩)就给你分配空间,代码块(🚂)结束,就自动回收空间

堆(heap):

可读可写

存储的是程序运行期间动态分配的 malloc/realloc 的空间

堆的(🛠)生存期随进程(👈)持续性,从 malloc/realloc 到 free 一直存在

下面是我们用 Borland C++ 编译过(🏊)后的结果

_TEXT   segment dword public use32 'CODE'_TEXT   ends_DATA   segment dword public use32 'DATA'_DATA   ends_BSS    segment dword public use32 'BSS'_BSS    ends

段定义( segment ) 是用来区分或者划分范围区域的意思。汇编语(🕍)言的 segment 伪指(📐)令表示段定义的起始,ends 伪指令表示段定义的结束。段定义是一段连续的内(🗓)存空间

所以内存针对自动增长的区域,会(🐢)有三种处理方式

如果一个进程与空闲区相邻,那么可把该空闲区分配给进(🏳)程以供其增大。如果进(🏈)程相邻的是另一个进程,就会有两种处理方式:(🚆)要么把需要增长的进程(✏)移动到一个内存中空闲区足够大的区域,要么把(🚺)一个或多个进程交换出去(🦔),已变成生成一个大的空闲区。如(🕍)果一个进程在内存中不能增长,而且磁(😷)盘上的交(👷)换区也满了,那么这个进程只有挂起一些空闲空间(或者可(〽)以结束该进程)

上面只针对单个或者一小部分需要增长的进程采用的方式,如果大部分进程都要在运行时增(🅰)长(⛷),为了减少因内存区域不够而引起的进程交换和移动所产生的开销,一(🔔)种可用的方法是,在换入或移动进程时为它分配一些额外的内存。然而,当进(🚑)程被换出到(🎰)磁盘上时,应该只交换实际上使用的内存,将额外的内存交换也是一种浪费,下面是一种为两个进程(➕)分配了增长空(🌃)间的内存配置。

如果进程有两个可(🔈)增长的段,例(🗯)如,供变量动态分配和释放的(🎢)作为堆(全局变量)使用的一个数据段(data segment),以及存放局部变量与返回地址的一个堆栈段(stack segment),就如图 b 所示(📆)。在图中可以看到所示进程的堆(🕙)栈段在进程所占(🎏)内存的顶端向下增长,紧接着在程序段后的数据段向上增长。当增(👆)长预留的(⭕)内(🏔)存区域不够了,处理方式就如上面的流程图(data segment 自动(🦕)增长的三种处理方式)一样了。

空闲内存管理

在进行内存动态分配时,操作系统必须对其进行(💥)管理。大致上(🆙)说,有两种监控内存使(♒)用(🍦)的方式

位图(bitmap)空闲列表(free lists)

下面我们就(🚟)来(🔋)探(🕣)讨一下这两种使用方式

使用位图的存储管理

使用位图方法时,内存可能被划分为小(🥕)到几个字或大到几千字节的分配单元。每个分配单元对应于位图中的一位,0 表示空闲, 1 表示占用(或者相反)。一块内存区域和其对应的位图如下

图 a 表示一段有 5 个进程和 3 个空闲区的内存,刻度为内存分配单元,阴影区表示空闲(在位图中用 0 表示(🕢));图 b 表示对应的位图;图 c 表示用链表表示同样的信息(🏜)

分(🚇)配单元的大小是一个重要的设计因素,分(🐫)配单位越小,位图越大。然而,即使只有 4 字节的分配单(🔻)元,32 位的内存也仅(🤘)仅只需要位图中(🎩)的 1 位。32n 位的内存需(👊)要 n 位的位图,所以1 个位图只占用了 1/32 的内存。如果选择更大的内存单元,位图应该要更小。如果进程的大小不是(🎃)分配单元的整数倍,那么在最后一个分配单元中会有大量的内存被浪费。

位图提供了一种简单的方法在固定大小的内存中跟踪内存的使用情况,因为位图(📘)的大小取决于内存和分配单元的大小。这种方法有一个问题是,当决定为把具有 k 个分配单元的进程放入内存时,内容管理器(💨)(memory manager) 必须搜索位图,在位图中找出能够运行 k 个连续 0 位的串(🎵)。在(🖌)位图中找出制定长度的连续 0 串是(🛴)一个很耗时的操作,这是位图的缺点。(可以简(🕉)单理解为在杂乱无章的数组中,找(🕸)出具有一大长串空闲的数组单元)

使(🕣)用链(♑)表进行管理

另一种记录内存使用情况的方法是,维护(🌺)一个记录已分配内存段和空闲内存段的链表,段会包含进程或者是两个进程的空闲区域。可用上面的图 c 来表示内存的使用情况。链表中的每一项都可以代表一个 空闲区(H) 或者是进程(P)的起始标志,长度和下一个链表项的位置。

在这个例子中,段链表(segment list)是按照地址排序的。这种方式的优点是,当进程终止或被交换时,更新列表很简单。一个终止进(🎀)程通常有两个邻居(除了内存的顶部和底部外)。相邻的可能是进程也可能是空闲区,它们有四种组(🏅)合方式(🍁)。

当按照地址顺序在链表中存放进程和空闲区时,有(📥)几种算法可以为创建的进程(或者从磁盘中换入的进程)分(⭐)配内存(⛩)。我们先假设内存管理器知道应该分配多少内存,最简单的算法是使用 首次适配(first fit)。内存管理器会沿着段列表进行扫描,直到找个一个足够大的空闲(🈵)区为止。除非空闲区大小和要(🍓)分配的空间大小一样,否(👫)则(🌈)将空闲区分为两部分,一部分供进程使用;一部分生成新的空闲区。首次适配算法是一种速度很快的算法,因为它会尽可能的搜索链表。

首(⌚)次适配的一个小的变体是 下次适配(next fit)。它和首次匹配的工作方式(😍)相同,只有(🔳)一个不同之(🔕)处那就是下次适配在每次找到合(💸)适的空闲区时就会记录当时的位置,以便下次寻找空闲区时从上次结束的地方(🖨)开始搜索,而不是像首次匹配算法那样每次都会从头开始搜索。Bays(1997) 证明了下次算法的性能略低于首次匹配算法。

另外一个著名的并且广泛使用的算法是 最佳适配(best fit)。最佳适配会从头到尾寻找整个链表,找出能够容纳进程的最小空闲区。最佳适(💏)配算法会试图找出最接近实际需要的空闲区,以最好的匹配请求和可用(💴)空闲区,而不是先一次拆分一个以后可能会用到的大的空闲区。比如现在我们需要一个大小为 2 的块(🚩),那么首次匹配算法会把这个块分配在位置 5 的空(⛔)闲区,而最佳适配算法(🆗)会把(📌)该块分配在位置为 18 的空闲区,如下

那么最佳适配算法的性能(🕢)如何呢?最佳适配会遍历整个链(📯)表,所以最佳适配算法的性能要比首次匹配算法差。但是令人想不到的是,最佳适配算法要比首次匹配和下(🆙)次匹配算法浪费更多的内存,因为它会(🕣)产生大量无用的小缓冲区,首次匹配算法(🍤)生成的空闲区会更大一些。

最佳适配的空闲区会分裂出很多非常(🔵)小的缓冲区,为了避免这一问题,可以考虑使用 最差适配(worst fit) 算法。即(⛽)总是分(🔬)配最大的内存区域(所以你现在明白为什么最佳适配算法(💬)会分裂出很多小缓冲区了吧)(♏),使新(🤭)分配(🥕)的空闲区比较大从而可以继续使用。仿真程序表明最差适配算(🏰)法也不是一个好主意。

如果为进程和空闲区维(🗞)护各自独立的链表,那么这四个算法的速度都能得到提高。这样,这四种算法的目标都是为了检查空闲区而不是进程。但这种分配速度的提高的一个不可避免的代价是增加复杂度和减慢内存释放速度,因为必须将一个回收的段从进程链表中删除并插入空闲链表区。

如果进程和空闲区使用不同(🌞)的链(🚪)表,那么可以按照大小对空闲区链表排序,以便提高最佳适配算法的速度。在使用最佳适配算法搜索由小到大排列(🌂)的空闲区链表时,只要找到一个合适的空闲区,则这个空(👂)闲区就是能容纳这个作业的最小空(🌹)闲区,因此是最佳(😩)匹配。因为空闲区链表以单链表形式组织,所以不(🐂)需要进一步搜索。空闲区链表按大小排序时,首次适配(🕠)算法与最佳适配算法(🌋)一样快,而下次适配算法在(⛅)这里毫无意义。

另一种分配算(📂)法是 快速适配(quick fit) 算法(😑),它为那些常用大小的空闲区维护单独的链表。例如,有一个 n 项的表,该表的第一项是指向大小为 4 KB 的空闲区链表表头指针,第二(✏)项是指向大小为 8 KB 的空(🛤)闲区链表表头指针,第三项(🧓)是指向大小为 12 KB 的(🔆)空闲区链表表头指针,以(🚅)此类推。比(🥛)如 21 KB 这样的空闲区既可以放在 20 KB 的链表中,也可以放在一个专门存放大小比较特别的(🛹)空闲区链(🏯)表中。

快速匹配算(📩)法寻找一个指定代销的空闲区也(😂)是十分快速的,但它和所有将空闲区(📱)按大小排序的方案一(🥣)样,都有一个共(📻)同的缺点,即在一个进程终(🛢)止或被换(💯)出时,寻找它的相邻块(🈹)并查看是否可以(📔)合(🔯)并的过(🧓)程都是(🥊)非常耗时的。如果不进行合并,内(🔴)存将会很快分裂出大量进程无法利用的小空闲区。


文章参考:

https://baike.baidu.com/item/内存/103614?fr=aladdin

https://baike.baidu.com/item/数据段/5136260?fromtitle=data%20segment&fromid=18082638&fr=aladdin

https://blog.csdn.net/One_L_Star/article/details/81901186

《现代操作系统》第四版

《Modern Operation System》fourth

https://baike.baidu.com/item/SATA硬盘/3947233?fr=aladdin

【饥荒手游下载ios的相关新闻】

猜你喜欢

  • 更新至06集

    喧哗独学

  • 更新至20240502期小屋特辑

    音你而来

  • 更新至第20240503期

    半熟恋人第三季

  • 更新至18集

    天行健

  • HD

    前途海量

  • 更新至08集

    老家伙

  • 更新至17集

    另一种蓝

  • 更新至05集

    庆余年第二季

  • 更新至02集

    9号秘事第九季

  • 更新至04集

    布里奇顿第三季

💟相关问题

1.请问哪个网站可以免费在线观看动漫《没有内存,怎么还能跑程序呢》?

优酷视频网友:http://www.youxise.com/%7Bxwd_%E5%BD%93%E5%89%8D%E8%B7%AF%E5%BE%84%7D/38221928.html

2.《没有内存,怎么还能跑程序呢》是什么时候上映/什么时候开播的?

腾讯视频网友:上映时间为2022年,详细日期可以去百度百科查一查。

3.《没有内存,怎么还能跑程序呢》是哪些演员主演的?

爱奇艺网友:没有内存,怎么还能跑程序呢演员表有,导演是。

4.动漫《没有内存,怎么还能跑程序呢》一共多少集?

电影吧网友:目前已更新到全集已完结

5.手机免费在线点播《没有内存,怎么还能跑程序呢》有哪些网站?

手机电影网网友:美剧网、腾讯视频、电影网

6.《没有内存,怎么还能跑程序呢》评价怎么样?

百度最佳答案:《没有内存,怎么还能跑程序呢》口碑不错,演员阵容强大演技炸裂,并且演员的演技一直在线,全程无尿点。你也可以登录百度问答获得更多评价。

  • 没有内存,怎么还能跑程序呢百度百科 没有内存,怎么还能跑程序呢版原著 没有内存,怎么还能跑程序呢什么时候播 没有内存,怎么还能跑程序呢在线免费观看 没有内存,怎么还能跑程序呢演员表 没有内存,怎么还能跑程序呢大结局 没有内存,怎么还能跑程序呢说的是什么 没有内存,怎么还能跑程序呢图片 在线没有内存,怎么还能跑程序呢好看吗 没有内存,怎么还能跑程序呢剧情介绍      没有内存,怎么还能跑程序呢角色介绍 没有内存,怎么还能跑程序呢上映时间 
  • Copyright © 2008-2018

    统计代码